#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

#define NOME_ARQ_ENTRADA "in.ppm"
#define NOME_ARQ_SAIDA "out.ppm"
#define NODES 5

typedef struct
{
    int R;
    int G;
    int B;
} pixel;

typedef struct
{
    pixel M[8000][3100];//M[X][Y]
    int nColunas;//X maximo
    int nLinhas;//Y maximo
    int max;//maior cor
    int st; //que linha comeca essa matriz
    int en;//que linha termina essa matriz
} matrizPixel;

int abreArquivo( FILE **arq )
{
    *arq = fopen( NOME_ARQ_ENTRADA, "r" );
    if( arq == NULL )
        return 0;
    return 1;
}

void geraArqSaida( matrizPixel *mp )
{
    
    FILE *arq = fopen( NOME_ARQ_SAIDA, "w+" );
    int i, j;

    fprintf(arq,"P3 %d %d %d\n",(*mp).nColunas, (*mp).nLinhas,(*mp).max);

    for( i = 0; i < (*mp).nLinhas; i++)
    {
        for( j = 0; j < (*mp).nColunas; j++ )
        {
           fprintf(arq,"%d %d %d\n", (*mp).M[i][j].R, (*mp).M[i][j].G, (*mp).M[i][j].B );
        }
    }
    fclose(arq);
}

pixel smooth( matrizPixel *mp, int x, int y )
{
    pixel resultado;
    int somatoriaR = 0, somatoriaG = 0, somatoriaB = 0;
    int i, j;

    //Smooth efetuado em grupo 5x5 de pixels
    for( i = x - 2; i <= x + 2; i++ )
    {
        for( j = y - 2; j <= y + 2; j++ )
        {

            if( i >= 0 && i < mp->nLinhas && j >= 0 && j < mp->nColunas )
            {
                /*Se a posicao existir na matriz, seus componentes sao
                adicionados na somatoria, caso contrario, eh assumido que
                seus componentes sao zerados, e entao nada sera feito*/
                somatoriaR += mp->M[i][j].R;
                somatoriaG += mp->M[i][j].G;
                somatoriaB += mp->M[i][j].B;
            }
        }
    }

    resultado.R = somatoriaR / 25;
    resultado.G = somatoriaG / 25;
    resultado.B = somatoriaB / 25;

    return resultado;
}

void smoothMatriz( matrizPixel *mp, matrizPixel *mps )
{
    //mps eh a nova matriz smoothed
    int i, j;
    (*mps).nColunas = (*mp).nColunas;
    (*mps).nLinhas = (*mp).nLinhas;
    (*mps).max = (*mp).max;
    (*mps).st = (*mp).st;
    (*mps).en = (*mp).en;

    for( i = (*mps).st; i <= (*mps).en; i++) //da smooth nesse trecho da matriz que esse cara eh responsavel
        for( j = 0; j < (*mps).nColunas; j++)
           (*mps).M[i][j] = smooth( mp, i, j );
}

void getMatrixFrom(matrizPixel * mp,  matrizPixel * mps,  int x, int y)
{
    //copia a matriz da linha x ateh a linha y
	int i,j;
	for (i = x; i <= y; ++i)
	{
		for (j = 0; j < (*mp).nColunas; ++j)
		{
			(*mps).M[i][j] = (*mp).M[i][j];
		}
	}
	return;
}


void geraMatrizImagem( FILE * arqImg, matrizPixel * mp)
{
    int i, j;

    fscanf( arqImg, "%*s %d %d %d", &(*mp).nColunas, &(*mp).nLinhas, &(*mp).max);

    //%*s: Pula o P3

    for( i = 0; i < (*mp).nLinhas; i++)
    {
        for( j = 0; j < (*mp).nColunas; j++ )
        {
            fscanf( arqImg, "%d%d%d", &(*mp).M[i][j].R, &(*mp).M[i][j].G, &(*mp).M[i][j].B );
        }
    }
}


int main(int argc, char ** argv)
{
    FILE *arq;
    matrizPixel * mp,  *mps, *retorno;
    mp = (matrizPixel*) malloc (sizeof(matrizPixel));
    mps = (matrizPixel*) malloc (sizeof(matrizPixel));
    retorno = (matrizPixel*) malloc (sizeof(matrizPixel));

    if( !abreArquivo( &arq ) )
    {
        printf("Arquivo nao encontrado");
        return 0;
    }

    //serialização das structs para poder passar a mensagem
    
    int rank, msg = 1, tag = 1, qntPorBloco, qntprocessos;
    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD,&qntprocessos);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    const int nitems = 3;
    int          blocklengths[3] = {1,1,1};
    MPI_Datatype types[3] = {MPI_INT, MPI_INT, MPI_INT};
    MPI_Datatype mpi_pixel_type;
    MPI_Aint     offsets[3];

    offsets[0] = offsetof(pixel, R);
    offsets[1] = offsetof(pixel, G);
    offsets[2] = offsetof(pixel, B);

    MPI_Type_create_struct(nitems, blocklengths, offsets, types, &mpi_pixel_type);
    MPI_Type_commit(&mpi_pixel_type);

    const int nitems2 = 6;
    int          blocklengths2[6] = {9000000, 1, 1, 1, 1, 1};
    MPI_Datatype types2[6] = {mpi_pixel_type, MPI_INT, MPI_INT, MPI_INT, MPI_INT, MPI_INT};
    MPI_Datatype mpi_matrizPixel_type;
    MPI_Aint     offsets2[6];

    offsets2[0] = offsetof(matrizPixel, M);
    offsets2[1] = offsetof(matrizPixel, nColunas);
    offsets2[2] = offsetof(matrizPixel, nLinhas);
    offsets2[3] = offsetof(matrizPixel, max);
    offsets2[4] = offsetof(matrizPixel, st);
    offsets2[5] = offsetof(matrizPixel, en);

    MPI_Type_create_struct(nitems2, blocklengths2, offsets2, types2, &mpi_matrizPixel_type);
    MPI_Type_commit(&mpi_matrizPixel_type);

    if (rank == 0)
    {
        //rank == 0 eh o pai
        int i,j,k;
        geraMatrizImagem(arq, mp);
        
        struct timeval time1,time2; 
        gettimeofday(&time1,NULL);
        
        qntprocessos--;
        qntPorBloco = ((*mp).nLinhas + qntprocessos - 1)/qntprocessos; //calcula quantas linhas tem por bloco

        for (i = 1; i <= qntprocessos; ++i)
        {
            int sti = (i - 1) * qntPorBloco;
            int stj = i * qntPorBloco;
           
            if (stj >= (*mp).nLinhas)
                stj = (*mp).nLinhas - 1;

            //passa algumas linhas a mais pra poder calcular o smooth
            if (sti - 2 >= 0 && stj + 2 < (*mp).nLinhas) 
            {
                getMatrixFrom(mp, mps, sti - 2, stj + 2);
            }
            else if (stj + 2 < (*mp).nLinhas)
            {
                getMatrixFrom(mp, mps, sti, stj + 2);
            }
            else if (sti - 2 >= 0)
            {
                getMatrixFrom(mp, mps, sti - 2, stj);  
            }
            else
            {
                getMatrixFrom(mp, mps, sti, stj);      
            }
            
            (*mps).st = sti;
            (*mps).en = stj;
            (*mps).nLinhas = (*mp).nLinhas;
            (*mps).nColunas = (*mp).nColunas;
            (*mps).max = (*mp).max;

       //     printf("liberando pro processo %d\n",i);
            int rc = MPI_Send(mps, 1, mpi_matrizPixel_type, i, tag, MPI_COMM_WORLD);
            //envia a matriz queo processo i vai ser responsavel
        }

        (*retorno).nLinhas = (*mp).nLinhas;
        (*retorno).nColunas = (*mp).nColunas;
        (*retorno).max = (*mp).max;

        for (i = 1; i <= qntprocessos; ++i)
        {
            MPI_Status status;
         //   printf("esperando o %do processo enviar\n",i);
            
            //fica esperando o resultado do i-th processo
            int rc = MPI_Recv(mps, 1, mpi_matrizPixel_type, MPI_ANY_SOURCE, tag, MPI_COMM_WORLD, &status);
            int id = status.MPI_SOURCE;
           // printf("recebido do processo %d\n",id);

            int sti = (id-1) * qntPorBloco;
            int stj = id * qntPorBloco;

            //copia o resultado pra uma matriz unica
            for (j = sti; j <= stj; ++j)
            {
                for (k = 0; k < (*mp).nColunas; ++k)
                {
                    (*retorno).M[j][k] = (*mps).M[j][k];
                }
            }
      
        }

        gettimeofday(&time2,NULL);
        printf("elapsed = %lfs\n",(double)(time2.tv_sec - time1.tv_sec));
        geraArqSaida(retorno);

    }

    if (rank != 0)
    {

        struct timeval time1,time2; 
        gettimeofday(&time1,NULL);
        int i,j;
        MPI_Recv(mp, 1, mpi_matrizPixel_type, 0, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        //recebe a matriz pra fazer smooth
//        printf("processo %d liberado\n",rank);

        for (i = 1; i <= qntprocessos; ++i)
        {
            if (rank == i)
            {
                smoothMatriz(mp, mps);
  //              printf("processo %d sending...\n",i);
                int rc = MPI_Send(mps, 1, mpi_matrizPixel_type , 0, tag, MPI_COMM_WORLD); //manda pro processo 0 o resultado
    //            printf("processo %d terminou\n",i);         
            }
        }


        gettimeofday(&time2,NULL);
        printf("elapsed = %lfs\n",(double)(time2.tv_sec - time1.tv_sec));
    }

    MPI_Finalize();
}
